/*
 *  stk3a5x_drv_i2c.c - Linux kernel modules for sensortek stk3a5x
 *  proximity/ambient light sensor (I2C Interface)
 *
 *  Copyright (C) 2012~2019 Taka Chiu, sensortek Inc.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include "stk3a5x.h"

static int stk3a5x_local_init(void);
static int stk3a5x_local_uninit(void);

struct alsps_init_info stk3a5x_init_info =
{
    .name = "stk3a5x",
    .init = stk3a5x_local_init,
    .uninit = stk3a5x_local_uninit,
};

static DRIVER_ATTR_RO(als);
static DRIVER_ATTR_RO(ps);
static DRIVER_ATTR_RW(config);
static DRIVER_ATTR_RW(lux);
static DRIVER_ATTR_RW(trace);
static DRIVER_ATTR_RO(status);
static DRIVER_ATTR_RW(send);
static DRIVER_ATTR_RW(recv);
static DRIVER_ATTR_RO(allreg);
static DRIVER_ATTR_RO(distance);
#ifdef STK_TUNE0
    static DRIVER_ATTR_RW(psmaxdiff);
    static DRIVER_ATTR_RW(psltnct);
    static DRIVER_ATTR_RW(pshtnct);
#endif

#if (defined(STK_ALS_CALI) || defined(STK_PS_CALI))
    static DRIVER_ATTR_RW(cali);
#endif

static struct driver_attribute *stk3a5x_attr_list[] =
{
    &driver_attr_als,
    &driver_attr_ps, 
    &driver_attr_config,
    &driver_attr_lux,
    &driver_attr_trace,
    &driver_attr_status,
    &driver_attr_send,
    &driver_attr_recv,
    &driver_attr_allreg,
    &driver_attr_distance,
#ifdef STK_TUNE0
    &driver_attr_psmaxdiff,
    &driver_attr_psltnct,
    &driver_attr_pshtnct,
#endif
#if (defined(STK_ALS_CALI) || defined(STK_PS_CALI))
    &driver_attr_cali,
#endif
};

static int create_attr(struct device_driver *driver)
{
    int idx, err = 0;
    int num = (int)(ARRAY_SIZE(stk3a5x_attr_list));

    if (driver == NULL)
    {
        return -EINVAL;
    }

    for (idx = 0; idx < num; idx++)
    {
        err = driver_create_file(driver, stk3a5x_attr_list[idx]);

        if (err)
        {
            APS_ERR("driver_create_file (%s) = %d\n", stk3a5x_attr_list[idx]->attr.name, err);
            break;
        }
    }

    return err;
}

static int delete_attr(struct device_driver *driver)
{
    int idx, err = 0;
    int num = (int)(sizeof(stk3a5x_attr_list) / sizeof(stk3a5x_attr_list[0]));

    if (!driver)
        return -EINVAL;

    for (idx = 0; idx < num; idx++)
    {
        driver_remove_file(driver, stk3a5x_attr_list[idx]);
    }

    return err;
}

int stk3a5x_create_attr(void)
{
    int ret = create_attr(&(stk3a5x_init_info.platform_diver_addr->driver));
    return ret;    
}

int stk3a5x_delete_attr(void)
{
    int ret = delete_attr(&(stk3a5x_init_info.platform_diver_addr->driver));
    return ret;
}

static int stk3a5x_reg_read(struct stk3a5x_data *alps_data,
                            unsigned char reg)
{
    int error = 0;
    mutex_lock(&alps_data->io_lock);
    error = i2c_smbus_read_byte_data(alps_data->client, reg);
    mutex_unlock(&alps_data->io_lock);

    if (error < 0)
    {
        APS_ERR("failed to read reg:0x%x\n", reg);
    }

    return error;
}

static int stk3a5x_reg_write(struct stk3a5x_data *alps_data,
                             unsigned char reg,
                             unsigned char val)
{
    int error = 0;
    mutex_lock(&alps_data->io_lock);
    error = i2c_smbus_write_byte_data(alps_data->client, reg, val);
    mutex_unlock(&alps_data->io_lock);

    if (error < 0)
    {
        APS_ERR("failed to write reg:0x%x with val:0x%x\n", reg, val);
    }

    return error;
}

static int stk3a5x_reg_write_block(struct stk3a5x_data *alps_data,
                                   unsigned char reg,
                                   unsigned char *val,
                                   unsigned char length)
{
    int error = 0;
    mutex_lock(&alps_data->io_lock);
    error = i2c_smbus_write_i2c_block_data(alps_data->client, reg, length, val);
    mutex_unlock(&alps_data->io_lock);

    if (error < 0)
    {
        APS_ERR("failed to write reg:0x%x\n", reg);
    }

    return error;
}
static int stk3a5x_reg_read_modify_write(struct stk3a5x_data *alps_data,
        unsigned char reg,
        unsigned char val,
        unsigned char mask)
{
    uint8_t rw_buffer = 0;
    int error = 0;

    if ((mask == 0xFF) || (mask == 0x0))
    {
        error = stk3a5x_reg_write(alps_data, reg, val);

        if (error < 0)
        {
            APS_ERR("failed to write reg:0x%x with val:0x%x\n", reg, val);
        }
    }
    else
    {
        rw_buffer = (uint8_t)stk3a5x_reg_read(alps_data, reg);

        if (rw_buffer < 0)
        {
            APS_ERR("failed to read reg:0x%x\n", reg);
            return rw_buffer;
        }
        else
        {
            rw_buffer = (rw_buffer & (~mask)) | (val & mask);
            error = stk3a5x_reg_write(alps_data, reg, rw_buffer);

            if (error < 0)
            {
                APS_ERR("failed to write reg(mask):0x%x with val:0x%x\n", reg, val);
            }
        }
    }

    return error;
}

static int stk3a5x_reg_read_block(struct stk3a5x_data *alps_data,
                                  unsigned char reg,
                                  int count,
                                  void *buf)
{
    int ret;
    ret = i2c_smbus_read_i2c_block_data(alps_data->client, reg, count, buf);
    return ret;
}

static const struct stk3a5x_bus_ops stk3a5x_i2c_bops =
{
    .bustype            = BUS_I2C,
    .write              = stk3a5x_reg_write,
    .write_block        = stk3a5x_reg_write_block,
    .read               = stk3a5x_reg_read,
    .read_block         = stk3a5x_reg_read_block,
    .read_modify_write  = stk3a5x_reg_read_modify_write,
};

static int stk3a5x_i2c_probe(struct i2c_client *client,
                             const struct i2c_device_id *id)
{
    stk3a5x_probe(client, &stk3a5x_i2c_bops);
    return 0;
}

static int stk3a5x_i2c_remove(struct i2c_client *client)
{
    return stk3a5x_remove(client);
}

int stk3a5x_i2c_suspend(struct device *dev)
{
    stk3a5x_suspend(dev);
    return 0;
}

int stk3a5x_i2c_resume(struct device *dev)
{
    stk3a5x_resume(dev);
    return 0;
}

int stk3a5x_i2c_detect(struct i2c_client *client, struct i2c_board_info *info)
{
    strlcpy(info->type, STK3A5X_DEV_NAME, sizeof(info->type));
    return 0;

}

#ifdef CONFIG_PM_SLEEP
static const struct dev_pm_ops stk3a5x_pm_ops =
{
    SET_SYSTEM_SLEEP_PM_OPS(stk3a5x_i2c_suspend, stk3a5x_i2c_resume)
};
#endif

static const struct i2c_device_id stk3a5x_i2c_id[] = 
{ 
    {STK3A5X_DEV_NAME, 0}, 
    {} 
};

#ifdef CONFIG_OF
static const struct of_device_id alsps_of_match[] =
{
    {.compatible = "mediatek,alsps"},
    {},
};
#endif

static struct i2c_driver stk3a5x_i2c_driver =
{
    .probe = stk3a5x_i2c_probe,
    .remove = stk3a5x_i2c_remove,
    .detect = stk3a5x_i2c_detect,
    .id_table = stk3a5x_i2c_id,
    .driver = {
        .name = STK3A5X_DEV_NAME,
#ifdef CONFIG_PM_SLEEP
        .pm   = &stk3a5x_pm_ops,
#endif
#ifdef CONFIG_OF
        .of_match_table = alsps_of_match,
#endif
    },
};

static int stk3a5x_local_uninit(void)
{
    APS_FUN();
    i2c_del_driver(&stk3a5x_i2c_driver);
    return 0;
}

static int stk3a5x_local_init(void)
{
    APS_FUN();

    if (i2c_add_driver(&stk3a5x_i2c_driver))
    {
        APS_ERR("add driver error\n");
        return -1;
    }

    if (-1 == stk3a5x_init_flag)
    {
        APS_ERR("stk3a5x_local_init fail with stk3a5x_init_flag=%d\n", stk3a5x_init_flag);
        return -1;
    }

    return 0;
}

static int __init stk3a5x_init(void)
{
    APS_FUN();
    alsps_driver_add(&stk3a5x_init_info);
    return 0;
}

static void __exit stk3a5x_exit(void)
{
    APS_FUN();
}

module_init(stk3a5x_init);
module_exit(stk3a5x_exit);
MODULE_AUTHOR("Sensortek");
MODULE_DESCRIPTION("SensorTek stk3a5x proximity and light sensor driver");
MODULE_LICENSE("GPL");